%%%-------------------------------------------------------------------
%%%    BASIC INFORMATION
%%%-------------------------------------------------------------------
%%% @copyright 2006 Erlang Training & Consulting Ltd
%%% @author  Martin Carlson <martin@erlang-consulting.com>
%%% @version 0.0.1
%%% @doc
%%% @end
%%%-------------------------------------------------------------------
-module(sql_pool_mgr_srv).

-behaviour(gen_server).

%% API
-export([start_link/0, 
	 allocate/0,
	 allocate/1,
	 deallocate/0,
	 deallocate/1, 
	 reference/0,
	 reference/1, 
	 add_resource/2,
	 add_resource/3, 
	 remove_resource/1,
	 remove_resource/2]).

%% gen_server callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
	 terminate/2, code_change/3]).

%% State
-record(state, {pools}).
-record(pool, {q = queue:new(), 
	       free = queue:new(), 
	       alloc = dict:new()}).

%% Servername
-define(SERVER, ?MODULE).


allocate() ->
    gen_server:call(?SERVER, {allocate, self(), default}, infinity).
allocate(Name) ->
    gen_server:call(?SERVER, {allocate, self(), Name}, infinity).


deallocate() ->
    gen_server:call(?SERVER, {deallocate, self(), default}).
deallocate(Name) ->
    gen_server:call(?SERVER, {deallocate, self(), Name}).

reference() ->
    gen_server:call(?SERVER, {reference, self(), default}).
reference(Name) ->
    gen_server:call(?SERVER, {reference, self(), Name}).

add_resource(Res, Mod) ->
    gen_server:call(?SERVER, {add_resource, Res, Mod, default}).
add_resource(Res, Mod, Name) ->
    gen_server:call(?SERVER, {add_resource, Res, Mod, Name}).

remove_resource(Res) ->
    gen_server:call(?SERVER, {remove_resource, Res, default}).
remove_resource(Res, Name) ->
    gen_server:call(?SERVER, {remove_resource, Res, Name}).

%%====================================================================
%% API
%%====================================================================
start_link() ->
    gen_server:start_link({local, ?SERVER}, ?MODULE, [], []).

%%====================================================================
%% gen_server callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State} |
%%                         {ok, State, Timeout} |
%%                         ignore               |
%%                         {stop, Reason}
%% Description: Initiates the server
%%--------------------------------------------------------------------
init([]) ->
    process_flag(trap_exit, true),
    {ok, #state{pools = dict:store(default, #pool{}, dict:new())}}.

%%--------------------------------------------------------------------
%% Function: %% handle_call(Request, From, State) -> {reply, Reply, State} |
%%                                      {reply, Reply, State, Timeout} |
%%                                      {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, Reply, State} |
%%                                      {stop, Reason, State}
%% Description: Handling call messages
%%--------------------------------------------------------------------
handle_call({reference, Pid, Name}, _From, #state{pools = Ps} = S) ->
    case dict:find(Name, Ps) of
	{ok, P} -> 
	    case dict:find(Pid, P#pool.alloc) of
		{ok, {Ref, Mod}} ->
		    {reply, {ok, Ref, Mod}, S};
		error ->
		    {reply, unbound, S}
	    end;
	error -> 
	    {reply, unbound, S}
    end;

%% Make sure we link clients to prevent leakage
%% TODO: Add check if client already got a resource
handle_call({allocate, Pid, Name}, From, #state{pools = Ps} = S) ->
    link(Pid),
    case dict:find(Name, Ps) of
	{ok, P} -> true;
	error -> P = #pool{}
    end,
    case queue:out(P#pool.free) of
	{{value, {Ref, Mod}}, NewQ} ->
	    Res = {Ref, Mod},
	    NP = P#pool{alloc = dict:store(Pid, Res, P#pool.alloc),
			free = NewQ},
	    {reply, {ok, Ref, Mod}, S#state{pools = dict:store(Name, NP, Ps)}};
	{empty, _} ->
	    NP = P#pool{q = queue:in({Pid, From}, P#pool.q)},
	    {noreply, S#state{pools = dict:store(Name, NP, Ps)}}
    end;         
	    
%% Unlink the client when no more reference are used
handle_call({deallocate, Pid, Name}, _From, #state{pools = Ps} = S) ->
    unlink(Pid),
    P = dict:fetch(Name, Ps),
    case dict:find(Pid, P#pool.alloc) of
	{ok, {Ref, Mod}} ->
	    Res = {Ref, Mod},
	    case queue:out(P#pool.q) of
		{{value, {NPid, From}}, NQ} ->
		    gen_server:reply(From, {ok, Ref, Mod}),
		    NA = dict:store(NPid, Res, dict:erase(Pid, P#pool.alloc)),
		    NP = P#pool{alloc = NA, q = NQ};
		{empty, _} ->
		    NP = P#pool{alloc = dict:erase(Pid, P#pool.alloc),
				free = queue:in(Res, P#pool.free)}
	    end,
	    {reply, ok, S#state{pools = dict:store(Name, NP, Ps)}};
	error ->
	    {reply, ok, S}
    end;    

handle_call({add_resource, Pid, Mod, Name}, _From, #state{pools = Ps} = S) ->
    link(Pid),
    case dict:find(Name, Ps) of
	{ok, P} -> true;
	error -> P = #pool{}
    end,
    Res = {Pid, Mod},
    case queue:out(P#pool.q) of
	{{value, {Ref, From}}, NewQ} ->
	    gen_server:reply(From, {ok, Pid, Mod}),
	    NP = P#pool{alloc = dict:store(Ref, Res, P#pool.alloc), q = NewQ};
	{empty, _} ->
	    NP = P#pool{free = queue:in(Res, P#pool.free)}
    end,
    {reply, ok, S#state{pools = dict:store(Name, NP, Ps)}};

handle_call({remove_resource, Pid, Name}, _From, #state{pools = Ps} = S) ->
    unlink(Pid),
    case dict:find(Name, Ps) of
	{ok, P} ->
	    {reply, ok, S#state{pools = dict:store(Name, filter(P, Pid), Ps)}};
	error ->
	    {reply, ok, S}	    
    end.

%%--------------------------------------------------------------------
%% Function: handle_cast(Msg, State) -> {noreply, State} |
%%                                      {noreply, State, Timeout} |
%%                                      {stop, Reason, State}
%% Description: Handling cast messages
%%--------------------------------------------------------------------
handle_cast(_Msg, State) ->
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: handle_info(Info, State) -> {noreply, State} |
%%                                       {noreply, State, Timeout} |
%%                                       {stop, Reason, State}
%% Description: Handling all non call/cast messages
%%--------------------------------------------------------------------

%% Handle exits form linked clients
handle_info({'EXIT', Pid, _}, #state{pools = P} = S) ->
    MP = dict:map(fun(_, Pool) -> filter(Pool, Pid) end, P),
    {noreply, S#state{pools = MP}};
handle_info(pool, State) ->
    error_logger:info_report({pool, dict:to_list(State#state.pools)}),
    {noreply, State};
handle_info(Info, State) ->
    error_logger:info_report({info, Info}),
    {noreply, State}.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description: This function is called by a gen_server when it is about to
%% terminate. It should be the opposite of Module:init/1 and do any necessary
%% cleaning up. When it returns, the gen_server terminates with Reason.
%% The return value is ignored.
%%--------------------------------------------------------------------
terminate(_Reason, _State) ->
    ok.


%%--------------------------------------------------------------------
%% Func: code_change(OldVsn, State, Extra) -> {ok, NewState}
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.


filter(Pool, Ref) ->
    Q = queue:to_list(Pool#pool.q),
    F = queue:to_list(Pool#pool.free),
    QF = fun(V) when V == Ref -> false;
	    ({V, _}) when V == Ref -> false;
	    (_) -> true
	 end,
    DF = fun(K,V) when K == Ref; V == Ref -> false;
	    (_,_) -> true
	 end,
    #pool{alloc = dict:filter(DF, Pool#pool.alloc),
	  q = queue:from_list(lists:filter(QF, Q)),
	  free = queue:from_list(lists:filter(QF, F))}.
    
